#include "list.h"

ListNode *ListNodeInit(losu_vm_t vm, Reference *ref, losu_object_t o) {
    ListNode *node = __xkit_malloc(sizeof(ListNode));
    node->value = *o;
    node->next = node;
    node->front = node;
    RefIncrease(vm, ref, o, 1);
    return node;
}

void ListNodeFree(losu_vm_t vm, Reference *ref, ListNode *node) {
    RefDecrease(vm, ref, &(node->value), 1);
    memset(node, 0, sizeof(ListNode));
    __xkit_free(node);
}

ListHeader *ListInit(losu_vm_t vm) {
    ListHeader *h = (ListHeader *)__xkit_malloc(sizeof(ListHeader));
    h->ref = RefInit(vm);
    h->length = 0;
    h->headNode = NULL;
    h->tailNode = NULL;
    return h;
}

void ListFree(losu_vm_t vm, ListHeader *h) {
    while (h->length) {
        ListRemove(vm, h, 0);
    }
    RefFree(vm, h->ref);
    __xkit_free(h);
}

void ListAddFirst(losu_vm_t vm, ListHeader *h, losu_object_t o) {
    if (h->headNode) {
        ListNode *node = ListNodeInit(vm, h->ref, o);
        node->next = h->headNode->front;
        h->headNode->front = node;
        h->headNode = node;
        (h->length) ++;
    } else {
        ListNode *node = ListNodeInit(vm, h->ref, o);
        h->headNode = node;
        h->tailNode = node;
        (h->length) = 1;
    }
}

void ListAddLast(losu_vm_t vm, ListHeader *h, losu_object_t o) {
    if (h->tailNode) {
        ListNode *node = ListNodeInit(vm, h->ref, o);
        node->front = h->tailNode;
        h->tailNode->next = node;
        h->tailNode = node;
        h->length ++;
    } else {
        ListNode *node = ListNodeInit(vm, h->ref, o);
        h->headNode = node;
        h->tailNode = node;
        h->length = 1;
    }
}

static ListNode *search(ListHeader *h, size_t index) {
    if (index == 0) {
        return h->headNode;
    } else if (index == h->length - 1) {
        return h->tailNode;
    } else {
        size_t half = h->length * 0.5;
        if (index <= half) {
            ListNode *node = h->headNode;
            size_t count = 0;
            while (count != index) {
                node = node->next;
                count ++;
            }
            return node;
        } else {
            ListNode *node = h->tailNode;
            size_t count = h->length - 1;
            while (count != index) {
                node = node->front;
                count --;
            }
            return node;
        }
    }
}

void ListInsert(losu_vm_t vm, ListHeader *h, size_t index, losu_object_t o) {
    if (index >= h->length) {
        __error_OutOfIndex(vm);
        return;
    }
    if (index == 0) {
        ListAddFirst(vm, h, o);
    } else if (index == h->length - 1) {
        ListAddLast(vm, h, o);
    } else {
        ListNode *oldNode = search(h, index);
        ListNode *newNode = ListNodeInit(vm, h->ref, o);
        ListNode *front = oldNode->front;
        oldNode->front = newNode;
        front->next = newNode;
        newNode->next = oldNode;
        newNode->front = front;
        h->length ++;
    }
}

void ListRmFirst(losu_vm_t vm, ListHeader *h, losu_object_t target) {
    if (h->length) {
        *target = h->headNode->value;
        if (h->length == 1) {
            ListNodeFree(vm, h->ref, h->headNode);
            h->headNode = NULL;
            h->tailNode = NULL;
            (h->length) = 0;
            return;
        }
        ListNode *next = h->headNode->next;
        ListNodeFree(vm, h->ref, h->headNode);
        next->front = NULL;
        h->headNode = next;
        (h->length) --;
    } else {
        __error_OutOfIndex(vm);
    }
}

void ListRmLast(losu_vm_t vm, ListHeader *h, losu_object_t target) {
    if (h->length) {
        *target = h->tailNode->value;
        if (h->length == 1) {
            ListNodeFree(vm, h->ref, h->tailNode);
            h->headNode = NULL;
            h->tailNode = NULL;
            h->length = 0;
            return;
        }
        ListNode *front = h->tailNode->front;
        ListNodeFree(vm, h->ref, h->tailNode);
        front->next = NULL;
        h->tailNode = front;
        (h->length) --;
    } else {
        __error_OutOfIndex(vm);
    }
}

void ListRemove(losu_vm_t vm, ListHeader *h, size_t index) {
    if (index >= h->length) {
        __error_OutOfIndex(vm);
        return;
    }
    if (index == 0) {
        losu_object temp;
        ListRmFirst(vm, h, &temp);
    } else if (index == h->length - 1) {
        losu_object temp;
        ListRmLast(vm, h, &temp);
    } else {
        ListNode *node = search(h, index);
        ListNode *front = node->front;
        ListNode *next = node->next;
        ListNodeFree(vm, h->ref, node);
        front->next = next;
        next->front = front;
        (h->length) --;
    }
}

void ListGet(losu_vm_t vm, ListHeader *h, size_t index, losu_object_t target) {
    if (index >= h->length) {
        __error_OutOfIndex(vm);
        return;
    }
    if (index == 0) {
        *target = h->headNode->value;
    } else if (index == h->length - 1) {
        *target = h->tailNode->value;
    } else {
        ListNode *node = search(h, index);
        *target = node->value;
    }
}

size_t ListSize(losu_vm_t vm, ListHeader *h) {
    (void)vm;
    return h->length;
}

